/*
 * @Author: wangjun haodreams@163.com
 * @Date: 2024-09-24 09:38:39
 * @LastEditors: wangjun haodreams@163.com
 * @LastEditTime: 2025-06-09 18:13:57
 * @FilePath: \libs\proxyhttp\proxy.go
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
package proxy

import (
	"bufio"
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"time"
)

var dial *proxy

// SetProxy 设置默认代理
func SetDefaultProxy(proxyServer string) {
	if dial != nil {
		dial.proxyServer = proxyServer
		return
	}
	dial = NewProxy(proxyServer)
}

// tcp 拨号
func DialTCP(address string, timeoutMs int64) (c net.Conn, err error) {
	if dial != nil {
		return dial.DialTCP(address, timeoutMs)
	}
	return net.DialTimeout("tcp", address, time.Duration(timeoutMs)*time.Millisecond)
}

// proxy 代理连接转发
type proxy struct {
	proxyServer string
	ssl         bool
	err         error
}

func (m *proxy) Error() error {
	return m.err
}

type Dial func(network, raddr string) (c net.Conn, err error)
type ContextDial func(ctx context.Context, network, address string) (c net.Conn, err error)

func DialFunc(uri string) (d Dial, err error) {
	m := NewProxy(uri)
	return m.Dial, m.err
}

func ContextDialFunc(uri string) (d ContextDial, err error) {
	m := NewProxy(uri)
	return m.DialContext, m.err
}

func NewProxy(uri string) *proxy {
	m := new(proxy)
	u, err := url.Parse(uri)
	if err != nil {
		m.err = err
		return m
	}
	switch u.Scheme {
	case "http":
		m.proxyServer = u.Host
		return m
	case "https":
		m.proxyServer = u.Host
		m.ssl = true
		return m
	case "":
		return m
	}
	m.err = errors.New("不支持的代理")
	return m
}

// Dial 代理连接
// addr 别名
func (m *proxy) Dial(network, addr string) (c net.Conn, err error) {
	return m.DialContext(context.TODO(), network, addr)
}

// DialContext .
func (m *proxy) DialContext(ctx context.Context, network, address string) (c net.Conn, err error) {
	netDialer := &net.Dialer{
		Timeout:   5 * time.Second,
		KeepAlive: 30 * time.Second,
	}
	if m.proxyServer == "" {
		if m.ssl {
			return tls.DialWithDialer(netDialer, network, address, &tls.Config{InsecureSkipVerify: true})
		}
		return netDialer.DialContext(ctx, network, address)
	}
	var conn net.Conn
	if m.ssl {
		d := new(tls.Dialer)
		d.NetDialer = netDialer
		d.Config = &tls.Config{InsecureSkipVerify: true}
		conn, err = d.DialContext(ctx, network, m.proxyServer)
	} else {
		conn, err = netDialer.DialContext(ctx, network, m.proxyServer)
	}
	if err != nil {
		return
	}

	err = nat(conn, address)
	if err != nil {
		conn.Close()
		return
	}
	c = conn
	return
}

// DialContext .
func (m *proxy) DialTCP(address string, timeoutMs int64) (c net.Conn, err error) {
	netDialer := &net.Dialer{
		Timeout:   time.Duration(timeoutMs) * time.Millisecond,
		KeepAlive: 30 * time.Second,
	}
	if m.proxyServer == "" {
		if m.ssl {
			return tls.DialWithDialer(netDialer, "tcp", address, &tls.Config{InsecureSkipVerify: true})
		}
		return netDialer.Dial("tcp", address)
	}
	var conn net.Conn
	if m.ssl {
		d := new(tls.Dialer)
		d.NetDialer = netDialer
		d.Config = &tls.Config{InsecureSkipVerify: true}
		conn, err = d.DialContext(context.TODO(), "tcp", m.proxyServer)
	} else {
		conn, err = netDialer.Dial("tcp", address)
	}
	if err != nil {
		return
	}

	err = nat(conn, address)
	if err != nil {
		conn.Close()
		return
	}
	c = conn
	return
}

func nat(conn net.Conn, address string) (err error) {
	req, err := http.NewRequest("CONNECT", "", nil)
	if err != nil {
		return
	}
	req.Host = address
	if err = req.Write(conn); err != nil {
		err = fmt.Errorf("写请求错误：%v", err)
		return
	}

	br := bufio.NewReader(conn)

	res, err := http.ReadResponse(br, req)
	if err != nil {
		err = fmt.Errorf("响应格式错误：%v", err)
		return
	}

	if res.StatusCode != 200 {
		err = fmt.Errorf("响应错误：%v", res)
		return
	}
	return
}
